%!Tex Program = xelatex
%\documentclass[a4paper]{article}
\documentclass[a4paper]{ctexart}
\usepackage{xltxtra}
\usepackage{listings}
%\setmainfont[Mapping=tex-text]{AR PL UMing CN:style=Light}
%\setmainfont[Mapping=tex-text]{AR PL UKai CN:style=Book}
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei:style=Regular}
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei Sharp:style=Regular}
%\setmainfont[Mapping=tex-text]{AR PL KaitiM GB:style=Regular} 
%\setmainfont[Mapping=tex-text]{AR PL SungtiL GB:style=Regular} 
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei Mono:style=Regular} 


\title{第三讲：Linux 环境和 Shell 编程}
\author{王何宇}
\date{}
\begin{document}
\maketitle
\pagestyle{empty}

\section{重定向、管道}

Shell 中一切都是文件，包括标准输入输出。比如 \verb|ls -l| 的输出结果默认是输出到 \verb|stdout|，
也就是标准输出设备，但你可以用重定向操作符将其重定向为一个文件，比如：
\begin{lstlisting}[language=sh]
ls -l > output.txt
\end{lstlisting}

这个就是把 \verb|ls -l| 的输出结果保存在 \verb|output.txt| 中。 

操作符 \verb|>| 是重写一个新文件，而 \verb|>>| 是追加在原文件末尾。
尝试一下。

我们知道除了标准输出文件 \verb|stdout|，还有一个标准错误文件 \verb|stderr|，
这个往往和 \verb|stdout| 指向同一个输出设备。但可以通过重定向分离。
一般默认的输出是标准输出文件，而 2 则值标准错误文件。比如：
\begin{lstlisting}[language=sh]
kill -HUP 1234
\end{lstlisting}
删除进程 1234, 显示
\begin{lstlisting}[language=sh]
bash: kill: (1234) - No such process
\end{lstlisting}
因为一般不会正好有这个进程号。这里全部都是标准错误文件输出的，所以如果运行
\begin{lstlisting}[language=sh]
kill -HUP 1234 > output.txt
\end{lstlisting}
则得到一个空文本文件。要
\begin{lstlisting}[language=sh]
kill -HUP 1234 2> output.txt
\end{lstlisting}
才能截获错误信息到文本文件。如果我们想同时截获标准输出和错误输出并放在一个文件里，可以
\begin{lstlisting}[language=sh]
kill -HUP 1234 >killout.txt 2>&1
\end{lstlisting}
注意，\verb|2>&1| 之间不能有空格。如果输出中有很多垃圾信息你不想看，
可以将其重定向到一个垃圾桶：
\begin{lstlisting}[language=sh]
kill -l 1234 2>/dev/null
\end{lstlisting}

既然标准输出可以重定向，标准输入自然也可以
\begin{lstlisting}[language=sh]
ls ~ -lR > output.txt
\end{lstlisting}
先搞一个大一点的输出，把它丢给 \verb|more| 做输入，功能是把输入一屏幕一屏幕输出。
\begin{lstlisting}[language=sh]
more < output.txt
\end{lstlisting}
注意这里操作符反向了。

然后这里有一件很土的事情，就是这个
\verb|output.txt| 就是一个中转站，何必一定要存下来呢？就不能直接转移过去？
也就是把一个命令的输出当作另一个命令的输入。这个当然可以，就叫管道，
操作符是 \verb|||。
\begin{lstlisting}[language=sh]
ls ~ -lR | more
\end{lstlisting}
大家可能注意到我曾经在输出中只显示有关键字的部分，用的也是管道：
\begin{lstlisting}[language=sh]
ls ~ -lR | grep usr
\end{lstlisting}
管道和重定向可以根据需要多重连接。

\section{循环过程}

Shell 提供了表示循环和分支判定的结构，如：
\begin{lstlisting}[language=sh]
for file in *
do
if grep -l figure $file
then
ls $file -l
fi
done
\end{lstlisting}
这里
\begin{lstlisting}[language=sh]
for file in *
do
...something...
done
\end{lstlisting}
是一个循环结构，\verb|file| 是一个用户定义的变量，
\verb|*| 表示当前目录下所有文件，\verb|in| 表示遍历。
所以这段命令的意思就是遍历当前目录下所有文件，对每个文件重复做
\verb|...something...| 这件事。比如：
\begin{lstlisting}[language=sh]
for fAd in *
do
echo $fAd
done
\end{lstlisting}
就是遍历当前目录，将全部文件名都打印一遍。命令 \verb|echo| 是输出的意思。

\section{分支过程}
然后看内层是一个分支结构：
\begin{lstlisting}[language=sh]
if [ ...condition... ]
then
...do something...
fi
\end{lstlisting}
以上是一个标准的 \verb|if ... fi| 结构。但我们用的时候没有
\verb|...condition...|，类似 C 语言，只要 \verb|...condition...| 部分不为空，
我们就认为条件是真。所以这里的意思是
\begin{lstlisting}[language=sh]
if grep -l figure $file
then
ls $file -l
fi
\end{lstlisting}
只要
\begin{lstlisting}[language=sh]
grep -l figure $file
\end{lstlisting}
过滤的结果不为空，即之前遍历到的 \verb|$file| 这些文件里存在包含 \verb|figure|
单词的，就把它的详细信息列出来。你自己可以根据需要做自由的调整，比如：
\begin{lstlisting}[language=sh]
for file in *
do
if grep -l figure $file
then
cp $file ~/Documents
fi
done
\end{lstlisting}
这是什么意思？已经具有收集功能了。

\section{通配符（wildcard expansion）}
注意到这里 \verb|*| 是一个典型的通配符，意思是全部文件；类似的通配符还有：
\begin{itemize}
\item \verb|?|，任一单个字符，比如：\verb|lec0?|，那么
  \verb|lec01|，\verb|lec02| 和 \verb|lec0k| 都匹配；
\verb|[]|，括号中任一字母。比如：\verb|lec0[abc]|，那么
\verb|lec0a|，\verb|lec0b| 和 \verb|lec0c| 都匹配；
\item \verb|[^]| 或 \verb|[!]|, 不含括号内的任一字符，比如：\verb|lec0[!12]|，
  那么 \verb|lec01| 和 \verb|lec02| 都不匹配；
\item \verb|{}|，包含大括号内任一字串，比如：\verb|lec03{.log,.pdf}| 则匹配
  \verb|lec03.pdf| 或 \verb|lec03.log|。
\end{itemize}
以上通配符可自由组合，比如：\verb|*0?.{t??,a*}|。

然后 \verb|$| 操作符主要有两个意思，要么是表示将一个变量替换成变量值，如之前的 \verb|$file|；
也可以表示对命令的替换。比如：
\begin{lstlisting}[language=sh]
grep -l crazyfish *
\end{lstlisting}
表示列出目录下所有含有 \verb|crazyfish| 字符串的文件名，那么
\begin{lstlisting}[language=sh]
ls -l $(grep -l crazyfish *)
\end{lstlisting}
就表示进一步列出这些文件的详细信息。

\section{脚本代码 (script)}

现在我们可以用连续的命令加上流程控制加上通配符实现比较复杂的功能。
这些命令可以形成一个脚本，并用 bash 去运行。具体做法是依次写下命令，
然后在第一行写上 \verb|#!/bin/bash| 表示这个脚本用哪个 shell 执行。
下面是一个完整的脚本的例子：
\begin{lstlisting}[language=sh]
#!/bin/sh
# first
# This file looks through all the files in the current
# directory for the string POSIX, and then prints the names of
# those files to the standard output.
for file in *
do
if grep -q crazyfish $file 
then
echo $file 
fi
done
exit 0
\end{lstlisting}
将它存成一个文本文件，比如叫 \verb|test.sh|，然后将其设置成可执行，
然后就可以像运行一个命令一样运行它。实际上，它构成一个组合命令，或称批处理。
这里 \verb|grep -q| 表示安静地运行，或者说，不要实际输出，因为我们这里只要求
\verb|grep| 返回是否找到这样一个状态就行。

\section{变量}

既然可以写脚本了，变量自然很重要。这里变量直接使用就可以：
\begin{lstlisting}[language=sh]
salutation=Hello
echo $salutation
\end{lstlisting}
一赋值一输出。默认就是字符串，引不引号无所谓：
\begin{lstlisting}[language=sh]
salutation="Yes Dear"
echo $salutation
\end{lstlisting}
就算是给数字，还是字符串：
\begin{lstlisting}[language=sh]
salutation=7+5
echo $salutation
\end{lstlisting}

命令 \verb|read| 可以由用户给变量赋值：
\begin{lstlisting}[language=sh]
read salutation
echo $salutation
\end{lstlisting}

从上面的过程我们可以看到，在 shell 中，双引号和不加引号都是字符串，
而单引号和 \verb|\| 才是真正的引号：
\begin{lstlisting}[language=sh]
#!/bin/sh
myvar="Hi there"
echo $myvar
echo "$myvar"
echo '$myvar'
echo \$myvar
echo Enter some text
read myvar
echo '$myvar' now equals $myvar
exit 0
\end{lstlisting}
(参见：example/scripts/quoting.sh)

\section{环境变量}

除了我们自定义的变量，shell 还内置了很多环境变量，供我们和系统交互。
\begin{itemize}
\item \verb|$HOME|，用户目录；
\item \verb|$PATH|，执行程序的路径，放在这些路径下的可执行文件能够直接被运行；
\item \verb|$PS1|，命令行提示符，普通用户是 \verb|$|，超级用户是 \verb|#|；
\item \verb|$PS2|，第二提示符，比如等待输入状态，通常是 \verb|>|；
\item \verb|$IFS|，输入参数的间隔符，一般是一个空格，也可以是 TAB 或换行；
\end{itemize}
以上都可以用 \verb|echo| 输出查看。但是
\begin{lstlisting}[language=sh]
echo $PS1
\end{lstlisting}
会得到
\begin{lstlisting}[language=sh]
${debian_chroot:+($debian_chroot)}\u@\h:\w\$
\end{lstlisting}
这里转义字符 \verb|\u| 表示用户名，\verb|\h| 表示主机名，\verb|\w| 表示当前路径。
以下环境变量专门给 scripts 使用，
\begin{itemize}
\item \verb|$0|，脚本名；
\item \verb|$#|，传递给脚本的参数个数；
\item \verb|$$|，脚本的进程编号；
\item \verb|$1|，\verb|$2|，...，传递给脚本的第 1 个，第 2 个参数，...；
\item \verb|$*|，传递给脚本的全部参数，作为一个字符串；
\item \verb|$@|，传递给脚本的全部参数，有几个参数就是几个字符串；
\end{itemize}
测试脚本(try\_var.sh)
\begin{lstlisting}[language=sh]
#!/bin/bash

echo “The program $0 is now running”
echo “The first parameter was $1”
echo “The second parameter was $2”
echo “The third parameter was $3”
echo “The all parameters were $*“

echo “The parameter list was:“
for para in $@
do
echo $para
done

exit 0
\end{lstlisting}
体会一下 \verb|@*| 和 \verb|$@| 的区别。

\section{判断条件（condition）}

实际上，正经的 \verb|if| 后面应该跟一个条件判断，条件的写法是：
\begin{lstlisting}[language=sh]
if ...condition...
\end{lstlisting}
这里 \verb|...condition...| 的有两种表达，\verb|test| 或者 \verb|[  ]|，
后者方括号两端必须各有一个空格。

\begin{table}[p!]
\caption{字符串逻辑运算关系表}
\centering
\begin{tabular}{|c|c|}
\hline
字符串比较 & 结果 \\
\hline
string1 = string2 & True 如果字符串相等 \\
\hline
string1 != string2 & True 如果字符串不想等 \\
\hline
-n string & True 如果字符串非空 \\
\hline
-z string & True 如果字符串空 \\
\hline
\end{tabular}
\end{table}

\begin{table}[p!]
  
\caption{算术逻辑运算关系表}
\centering
\begin{tabular}{|c|c|}
\hline
算术表达式 & 结果 \\
\hline
expression1 -eq expression2 & True 如果表达式值相等\\
\hline
expression1 -ne expression2 & True 如果表达式值不想等\\
\hline
expression1 -gt expression2 & True 如果 expression1 大于 expression2\\
\hline
expression1 -ge expression2 & True 如果 expression1 大于等于 expression2\\
\hline
expression1 -lt expression2 & True 如果 expression1 小于 expression2\\
\hline
expression1 -le expression2 & True 如果 expression1 小于等于 expression2\\
\hline
! expression & True 如果 expression false, 或反之\\
\hline
\end{tabular}
\end{table}

\begin{table}[p!]
  \caption{文件逻辑运算关系表}
\centering
\begin{tabular}{|c|c|}
\hline
文件判断 & 结果 \\
\hline
-d file & True 如果 file 是目录\\
\hline
-e file & True 如果 file 存在\\
\hline
-f file & True 如果 file 是正经的文件\\
\hline
-g file & True 如果 file 设置了 group id\\
\hline
-r file & True 如果 file 可读\\
\hline
-s file & True 如果 file 大小不是零\\
\hline
-u file & True 如果 file 设置了 user id\\
\hline
-w file & True 如果 file 可写\\
\hline
-x file & True 如果 file 可执行\\
\hline
\end{tabular}
\end{table}
\verb|set-group-id|和\verb|set-user-id|的区别是前者给予程序的是其所属组的权限，后者给予所有者的权限。其标志>对于包含shell脚本的文件没有影响，只对可执行二进制文件有影响。下面是一个如何测试\verb|/bin/bash|文件状态的例子

\begin{lstlisting}[language=sh]
#!/bin/sh
if [ -f /bin/bash ]
then
echo "file /bin/bash exists"
fi
if[ -d /bin/bash ]
then
echo "/bin/bash is a directory"
else
echo "/bin/bash is NOT a directory"
fi
\end{lstlisting}

无论测试是否为真，该测试要求文件存在。接下来我们来看条件的控制结构。\\
与其他编程语言很相似，shell有一组控制结构。
if语句很简单，它测试命令结果，然后有条件的执行一组语句
\begin{lstlisting}[language=sh]
if condition
then
statements
else
statements
fi
\end{lstlisting}

if的通常用法是问一个问题然后根据答案做决定：
\begin{lstlisting}[language=sh]
#!/bin/sh
echo 'Is it morning?Please answer yes or no'
read timeofday
if[ $timeofday = "yes" ]; then
echo"Good morning"
else
echo"Good afternoon"
fi
exit 0
\end{lstlisting}
注意，在if中我们使用了额外的空格，但shell将会忽略。

elif 
在上个例子中，如果我们回答任何除了yes之外的答案，他都会当作no来处理。为了避免这种情况，我们可以使用elif，更改代码为：
\begin{lstlisting}[language=sh]
#!/bin/sh
echo 'Is it morning?Please answer yes or no'
read timeofday
if[ $timeofday = "yes" ]; then 
echo"Good morning"
elif [ $timeofday = "no" ];then
echo "Good afternoon"
else
echo "Sorry, $timeofday not recognized, Enter yes or no"
exit 1
fi
exit 0
\end{lstlisting}
其中，若第一个if条件为假，elif命令将再次测试变量，若仍为假，将返回1,调用者可通过该值来检验脚本是否成功。
上文通过elif我们修复了最明显的问题，但只按enter键，我们会得到错误的信息。问题源于第一个if中，测试变量timeofday时，他有空字符串组成，导致其看起来是\verb|if[="Yes"]|,并非有效条件，所以我们必须在变量周围使用引号，从而获得有效的测试。

for
使用for构造循环遍历一个值的范围。
语法如下：
\begin{lstlisting}[language=sh]
for variable in values
do
statemente
done
\end{lstlisting}

使用带有通配符展开的for循环：原始示例中，我们看到脚本使用shell展开，\verb|*|展开为当前目录下所有文件的名称。这些文件依次被用作for循环中的变量\verb|$file|
那么若你想打印当前目录中所有以字母f开头的脚本文件，并且所有的脚本都以\verb|.sh|结尾，你可以：
\begin{lstlisting}[language=sh]
#!/bin/sh
for file in $(ls f*.sh); do
lpr $file
done
exit 0
\end{lstlisting}
上例中shell展开\verb|f*.sh|,给出与该模式匹配的所有文件的名称。

while
但for在未知循环次数时就不那么有用了。这时就需要while语法。看下面的例子：
\begin{lstlisting}[language=sh]
#!/bin/sh
echo "Enter password"
read trythis
while [ "$trythis" != "secret" ]; do
echo “Sorry, try again”
read trythis
done
exit 0
\end{lstlisting}
这个脚本将会输出：
\begin{lstlisting}[language=sh]
Enter password
password
Sorry, try again
secret
\end{lstlisting}
由上例，我们可以看出，while语句在do和done之间的语句会持续执行。

until
until语法如下：
\begin{lstlisting}[language=sh]
until condition
do
statements
done
\end{lstlisting}
until与while循环非常相似，但其条件相反：until一直持续到条件为真，而非在条件为真时运行。若一个循环需至少循环一次，使用while，若可能根本不需要执行，则使用until循环。

case
case语法看起来有点吓人，但他可以将变量的内容与模式相匹配，根据匹配的模式允许执行不同的语句，其语法如下：
\begin{lstlisting}[language=sh]
case variable in
pattern [ | pattern] ...) statements;;
pattern [ | pattern] ...) statements;;
...
esac
\end{lstlisting}
以双分号结尾使得你可以在每个模式和下一个模式之间放置多个语句。我们可以通过接下来的三个例子进一步了解case语法。
case1：
\begin{lstlisting}[language=sh]
#!/bin/sh
echo "Is it morning? Please answer yes or no"
read timeofday
case "$timeofday" in
yes) echo "Good Morning";;
no ) echo "Good Afternoon";;
y ) echo "Good Morning";;
n ) echo "Good Afternoon";;
* ) echo "Sorry, answer not recognized";;
esac
exit 0
\end{lstlisting}
case2:
\begin{lstlisting}[language=sh]
#!/bin/sh
echo "Is it morning? Please answer yes or no"
read timeofday
case "$timeofday" in
yes | y | Yes | YES ) echo "Good Morning";;
n* | N* ) echo "Good Afternoon";;
* ) echo "Sorry, answer not recognized";;
esac
exit 0
\end{lstlisting}
把模式放在一起使用我们可以获得更简洁的版本。
case3：
\begin{lstlisting}[language=sh]
#!/bin/sh
echo "Is it morning? Please answer yes or no"
read timeofday
case "$timeofday" in
yes | y | Yes | YES )
echo "Good Morning"
echo "Up bright and early this morning"
;;
[nN]*)
echo "Good Afternoon"
;;
*)
echo "Sorry, answer not recognized"
echo "Please answer yes or no"
exit 1
;;
esac
exit 0
\end{lstlisting}

list
list语法可以将命令串联起来，比如在执行语句前满足几个不同的条件，或者一系列条件中至少有一个为真，可以用AND列表和OR列表。
AND列表：AND列表结构可以执行一系列命令，只有前面的命令都成功才会执行下一命令，语法为\verb|statement1 && statement2 && statement3 && ...|
\begin{lstlisting}[language=sh]
#!/bin/sh
touch file_one
rm -f file_two
if [ -f file_one ] && echo "hello" && [ -f file_two ] && echo " there"
then
echo "in if"
else
echo "in else"
fi
exit 0
\end{lstlisting}
在上述脚本中，我们\verb|touch file_one|,删除——two，然后AND列表就会测试每个文件是否存在。

OR列表：OR列表与上述不同，当他执行的命令成功后，就不再执行，语法为\verb|statement1 || statement2 || statement3 || ...|，其执行下一条语句的条件是前一条必须失败。
\begin{lstlisting}[language=sh]
#!/bin/sh
rm -f file_one
if [ -f file_one ] || echo "hello" || echo " there" 
then
echo "in if"
else
echo "in else"
fi
exit 0
\end{lstlisting}

语句块
若想要在只允许使用一条语句的地方使用多条语句，比如在AND或OR列表中，可以用大括号将其扩起来。

functions
你可以将一个大脚本拆分成许多小脚本，每个小脚本执行一个小任务。但第二个脚本要慢的多。定义函数时，只需在名称后加上空括号，并用语句将大括号扩起来。在定义函数时，需要将函数放在调用之前定义（由于脚本由顶部开始执行）。函数调用时，原本的参数会被函数中的参数所取代，函数完成后，又会恢复到原来的值。然后利用\verb|return|返回值，也可以回显字符串并获得结果。你可以通过\verb|local|关键字在shell函数中声明局部变量，否则，该函数可访问本质上是全局的shell变量。另外，若局部变量与全局变量有相同的名称，他将覆盖全局变量（仅在函数内）。
\begin{lstlisting}[language=sh]
#!/bin/sh
sample_text="global variable"
foo() {
local sample_text="local variable"
echo "Function foo is executing"
echo $sample_text
}
echo "script starting"
echo $sample_text
foo
echo "script ended" 
echo $sample_text
exit 0
\end{lstlisting}
若没有指定返回值的返回命令，则函数返回最后执行的命令的退出状态。

返回值
接下来的脚本展示了如何传递函数的参数以及如何返回True和False结果。
首先，在shell头文件之后，定义函数\verb|yes_or_no|:
\begin{lstlisting}[language=sh]
#!/bin/sh
yes_or_no() {
echo "Is your name $* ?"
while true
do
echo -n "Enter yes or no: "
read x
case "$x" in
y | yes ) return 0;;
n | no ) return 1;;
* ) echo “Answer yes or no"
esac
done
}
\end{lstlisting}
接着主要部分开始了
\begin{lstlisting}[language=sh]
echo “Original parameters are $*"
if yes_or_no "$1"
then
echo "Hi $1, nice name"
else
echo "Never mind"
fi
exit 0
\end{lstlisting}
脚本执行时，函数被定义但尚未执行。if语句中，函数被执行，最后返回一个值。根据返回值，if语句执行相应的命令。

commands
命令分为内置（内部）和普通（外部）两种。内置命令是在shell内部实现的，不能作为外部程序调用。然而，大部分内部命令也作为独立程序提供。但这并不重要，除非内部命令执行的更有效。

break
在满足条件之前，可以使用break从封闭的while,for,until中转义。

:命令
冒号命令为空命令，有助于简化条件的逻辑，相当于true。由于其为内置命令，运行速度快的多，但其输出可读性也差的多
\begin{lstlisting}[language=sh]
#!/bin/sh
rm -f fred
if [ -f fred ]; then
:
else
echo file fred did not exist
fi
exit 0
\end{lstlisting}

continue
该命令使得循环在下一次迭代时继续执行。
\begin{lstlisting}[language=sh]
#!/bin/sh
rm -rf fred*
echo > fred1
echo > fred2
mkdir fred3
echo > fred4
for file in fred*
do
if [ -d “$file” ]; then
echo “skipping directory $file”
continue
fi
echo file is $file
done
rm -rf fred*
exit 0
\end{lstlisting}
Continue可以将继续的封闭循环编号作为可选参数，以便部分地跳出循环。但它会使得脚本更难理解，所以很少使用。

.命令
该命令会执行当前shell中的命令：\verb|../shell_script|
通常，当脚本执行外部命令或者脚本时，会创建一个新环境，在新环境中执行然后丢弃该命令，除了返回到原环境的退出代码。而该命令允许执行的脚本改变当前的环境，可以使用它将变量和函数定义合并到脚本中。\\
若有两个命令，其中包含两个不同开发环境的环境设置。要为\verb|classic_set|设置环境，可以使用：
\begin{lstlisting}[language=sh]
#!/bin/sh
version=classic
PATH=/usr/local/old_bin:/usr/bin:/bin:.
PS1="classic> "
\end{lstlisting}
对于新命令，可以使用\verb|latest_set|
\begin{lstlisting}[language=sh]
#!/bin/sh
version=latest
PATH=/usr/local/new_bin:/usr/bin:/bin:.
PS1=" latest version> "
\end{lstlisting}

echo
Linux抑制换行字符的方法是
\begin{lstlisting}[language=sh]
echo -n "string to output"
\end{lstlisting}
但也经常会有
\begin{lstlisting}[language=sh]
Echo-e "string to output\c"
\end{lstlisting}
第二个选项确保了对反斜杠转移字符的解释。

eval
该命令允许对参数求值，通常不会作为单独的单位存在。他使代码能够生成且实时运行。

exec
该命令有两种不同的用途。典型用途：用不同的程序替换当前的shell，如\verb|exec wall "Thanks for your fish"|。在副本中将shell替换为wall，原本脚本中ecec后面的任何都不会被处理，因为执行脚本的shell已经不存在了;第二个用途：修改当前文件描述符：\verb|Exec 3< afile|。

exit n
exit命令会使脚本退出，若在交互式shell的命令提示符中使用，会被注销。若你允许你的脚本在没有制定推出状态的情况下退出，那么脚本中执行的最后一个命令的状态将被用作返回值。
shell中，退出码0表示成功，1到125是可以使用的错误码，剩下的值有保留意义，如下图所示
\begin{table}[ht]
\caption{}
\centering
\begin{tabular}{|c|c|}
\hline
	Exit Code & Description \\
\hline
	126 & The file was not executable \\
\hline
127 & A command was not found \\
\hline
128 and above & A signal occurred \\
\hline
\end{tabular}
\end{table}

export
export命令使被命名为其参数的变量在子shell中可用。该命令从它的参数创建一个环境变量，该环境变量可以被从当前程序调用的其他脚本和程序看到。技术上讲，导出的变量构成了从shell派生的任何子进程中的环境变量。
首先，列出 export 2:
\begin{lstlisting}[language=sh]
#!/bin/sh
echo "$foo"
echo "$bar"
\end{lstlisting}
接着在该脚本的末尾调用export 2:
\begin{lstlisting}[language=sh]
#!/bin/sh
foo=”The first meta-syntactic variable”
export bar=”The second meta-syntactic variable”
export2
\end{lstlisting}
export2脚本只是回显这两个变量的值。export1脚本设置了两个变量表，但只将bar标记为已导出，因此当它随后调用export1时，foo的值丢失了，但bar的值已导出到第二个脚本。出现空行是因为\verb|$foo|求值为零，而回显null变量会产生换行符。一旦变量从shell导出，它将被导出到从该shell调用的任何脚本中，并依次导出到它们调用的任何shell中，依此类推。如果脚本export2调用另一个脚本，它也将具有bar的值。

expr
该命令将其参数计算为表达式。常用于以下形式的简单算术：
\begin{lstlisting}[language=sh]
x=`expr $x + 1`
\end{lstlisting}
expr可执行求值的表达式如下表所示：
\begin{table}[ht]
\caption{}                    
\centering
\begin{tabular}{|c|c|}   
\hline
Expression Evaluation & Description \\
\hline
expr1 | expr2 \\ expr1 if expr1 is nonzero,otherwise expr2 \\
\hline
expr1 \& expr2 & Zero if either expression is zero, otherwise expr1 \\    
\hline
	expr1 = expr2 & Equal \\
\hline
	expr1 > expr2 & Greater than \\
\hline
	expr1 >= expr2 & Greater than or equal to \\
\hline
	expr1 < expr2 & Less than \\
\hline
	expr1 <= expr2 & Less than or equal to \\
\hline
	expr1 != expr2 & Not equal \\
\hline
	expr1 + expr2 & Addition \\
\hline
	expr1 - expr2 & Subtraction \\
\hline 
	expr1 * expr2 & Multiplication \\
\hline 
	expr1 / expr2 & Integer division \\
\hline
	expr1 \% expr2 & Integer modulo \\
\hline
\end{tabular}
\end{table}

Printf
该命令仅在较新的shell中可用。语法如下\verb|printf “format string“ parameter1 parameter2 ...|,不支持浮点数，因为shell中的所有算术运算都以整数形式执行。格式化字符由文字字符、转义序列和转换说明符的任意组合组成。格式字符串中除了 \% 和 \\ 意外的所有字符都会在输出中逐字显示。
支持以下转义序列：

\begin{table}[ht]
\caption{}                    
\centering
\begin{tabular}{|c|c|}   
\hline
        Escape Sequence & Description \\
\hline
        \\" & Double quate \\
\hline
	\\\\ & Backslash character \\    
\hline
	\\a & Alter(ring the bell or beep) \\
\hline
	\\b & Backespace character \\
\hline
	\\c & Suppress further output \\
\hline
	\\f & Form feed character \\
\hline
	\\n & Newline character \\
\hline
	\\r & Carriage return \\
\hline
	\\t & Tab character \\
\hline
	\\v & Vertical tab character \\
\hline
	\\ooo & The single character with octal value ooo \\
\hline
	\\xHH & The single character with the hexadedcimal value HH \\
\hline
\end{tabular}
\end{table}
转换说明符由%和字符组成，后跟一个转换字符，如下图所示：
\begin{table}[ht]
\caption{}                    
\centering
\begin{tabular}{|c|c|}   
\hline
        Conversion Specifier & Description \\
\hline
        D & Out put a decimal number. \\
\hline
	C & Out put a character. \\    
\hline
	S & Output a string. \\
\hline
	% & Output the % character. \\
\end{tabular}
\end{table}

return
该命令导致函数返回。

set
若想在shell脚本中使用当前月份的名称，系统提供了date命令，该命令假设您希望在shell脚本中使用当前月份的名称。系统提供了一个date com命令，该命令以字符串形式包含月份，但需要将其与其他字段分开。您可以使用set命令和\$(…)结构的组合来执行date命令并返回结果(稍后将更详细地描述)。date命令输出的第二个参数是月份字符串.
\begin{lstlisting}[language=sh]
#!/bin/sh
echo the date is $(date)
set $(date)
echo The month is $2
exit 0
\end{lstlisting}
注意：现实中应使用\verb|date+8B|

shift
shift命令将所有参数变量向下移动一个，这样\verb|$2|变成\verb|$1|，\verb|$3|变成\verb|$2|，以此类推。先前的\verb|$1|被丢弃，而\verb|$0|保持不变。如果在shift调用中指定了一个数值参数ter，则参数移动那么多个空格。其他变量\verb|$*|、\verb|$@|和\verb|$#|也根据参数变量的新排列进行了修改。
Shift通常用于扫描传递到脚本中的参数，如果脚本需要10个或更多参数，需要shift才能访问第10个或更多参数。例如，你可以像下面这样扫描所有的位置参数：
\begin{lstlisting}[language=sh]
#!/bin/sh
while [ “$1” != “” ]; do
echo “$1”
shift
done
exit 0
\end{lstlisting}

trap
trap命令用于指定接收信号时要采取的操作，本书后面将详细介绍这些操作。一个常见的用途是在脚本被中断时整理脚本.trap传递要采取的操作，然后是要捕获的信号名称（或多个）。由于脚本是由上到下解释的，所以必须在希望保护的脚本部分之前指定trap命令。要将trap条件重置为默认值，只需将命令指定为\verb|-|。要忽略一个信号，将\verb|com-l|命令设置为空字符串\'。不带参数的trap命令将打印出当前的trap和动作列表。
下表列出了X/Open标准涵盖的可捕获的较重要信号：
\begin{table}[ht]
\caption{}                    
\centering
\begin{tabular}{|c|c|}   
\hline
        Signal & Description \\
\hline
	HUP(1) & The file was not executable \\
\hline
	INT(2) & A command was not found \\    
\hline
	QUIT(3) & Quit;usually sent by pressing Ctrl+\ \\
\hline
	ABRT(6) & Abort;usually sent on some serious execution error \\
\hline
	ALRM(14) & Alarm;usually used for handing timeouts \\
\hline
	TERM(15) & Terminate;usually sent by the system when it is shutting down \\
\hline
\end{tabular}
\end{table}
下面的脚本演示了一些简单的信号接受：
\begin{lstlisting}[language=sh]
#!/bin/sh
trap ‘rm -f /tmp/my_tmp_file_$$’ INT
echo creating file /tmp/my_tmp_file_$$
date > /tmp/my_tmp_file_$$
echo “press interrupt (CTRL-C) to interrupt ....”
while [ -f /tmp/my_tmp_file_$$ ]; do
echo File exists
sleep 1
done
echo The file no longer exists
trap INT
echo creating file /tmp/my_tmp_file_$$
date > /tmp/my_tmp_file_$$
echo “press interrupt (control-C) to interrupt ....”
while [ -f /tmp/my_tmp_file_$$ ]; do
echo File exists
sleep 1
done
echo we never get here
exit 0
\end{lstlisting}
如果你运行这个脚本，按住Ctrl，然后在每个循环中按C(或任何你的中断键组合)，你会得到以下输出:
\begin{lstlisting}[language=sh]
creating file /tmp/my_tmp_file_141
press interrupt (CTRL-C) to interrupt ....
File exists
File exists
File exists
File exists
The file no longer exists
creating file /tmp/my_tmp_file_141
press interrupt (CTRL-C) to interrupt ....
File exists
File exists
File exists
File exists
\end{lstlisting}

unset
unset命令用来从环境中删除变量或函数。它不能对shell本身定义的只读变量这样做，比如IFS。它不常被使用。
下面的脚本一次写入Hello World，第二次写入换行符:
\begin{lstlisting}[language=sh]
#!/bin/sh
foo=”Hello World”
echo $foo
unset foo
echo $foo
\end{lstlisting}
写入\verb|foo=|与之前的放弃程序中的unset效果非常相似，但并不完全相同。写入\verb|foo=|的效果是将foo设置为null，但foo仍然存在。
使用\verb|unset foo|则会将变量foo从环境中移除。

两个更有用的命令和正则表达式
在了解如何使用这些shell编程的新知识之前，让我们看一下其他几个非常有用的命令，它们虽然不是shell的一部分，但在编写shell程序时通常很有用。在此过程中，我们还将研究正则表达式，这是一种模式匹配特性，在Linux及其相关程序中随处可见
find
第一个命令是find。这个用于搜索文件的命令非常有用，但是Linux新手经常发现它使用起来有点棘手，尤其是因为它接受选项、测试和操作类型的参数，而且一个参数的结果可能会影响后续参数的处理。其完整语法如下：\verb|find [path] [options] [tests] [actions]|.你可以使用绝对路径，相对路径，如果需要也可以制定多个路径。
几个主要的选项如下表所示：
\begin{table}[ht]
\caption{}                    
\centering
\begin{tabular}{|c|c|}   
\hline
        Option & Meaning \\
\hline
        -depth & Search the contents of a directory before looking at the directory itself \\
\hline
	-follow & Follow symbolic links \\    
\hline
	-maxdepths N & Search at most N levels of the directory when searching. \\
\hline
	-mount(or -xdev) & Don't search directories on other file systems. \\
\hline
\end{tabular}
\end{table}
现在进行测试。可以给出大量的测试来查找，每个测试返回true或false。当find工作时，它依次考虑它找到的每个文件，并按照定义的顺序在该文件上应用每个测试。如果测试返回false，则find停止考虑当前正在查看的文件并继续前进;如果测试返回true，则find处理当前当前文件上的下一个测试或操作。下表中列出较常见的部分测试：
\begin{table}[ht]
\caption{}                    
\centering
\begin{tabular}{|c|p{5cm}|}   
\hline
        Test & Meaning  \\
\hline
        -atime N & The file was last accessed N days ago. \\
\hline
	-mtime N & The file was last modified N days ago. \\    
\hline
	-name pattern & The name of the file, excluding any path, matches the pattern provided. To ensure that the pattern is passed to find, and not evaluated by the shell immediately, the pattern must always be in quotes. \\
\hline
	-newer otherfile & The file is newer than the file otherfile. \\
\hline
	-type C &  C The file is of type C, where C can be of a particular type; the most common are “d” for a directory and “f” for a regular file. For other types consult the manual pages. \\
\hline
	-user username & The file is owned by the user with the given name. \\
\hline
\end{tabular}
\end{table}
还可以使用操作符组合测试。大多数有长、短两种形式，如下表所示:
\begin{table}[ht]
\caption{}                    
\centering
\begin{tabular}{|c|c|c|}   
\hline
	Operator,Short Form & Operator,Long Form & Meaning \\
\hline
	! & -not & Invest the test \\
\hline
	-a & -and & Both tests must be true \\ 
\hline
	-o & -or & Either test must be true \\
\hline
\end{tabular}
\end{table}
可以使用括号强制设置测试和操作符的优先级。由于这些对shell有特殊的含义，因此还必须使用反斜杠引用大括号。此外，如果对文件名使用pat模式，则必须使用引号，这样shell就不会展开该名称，而是将其直接传递给find命令。
指定find应该在当前目录(.)中搜索比文件while2 (-newer while2)更新的文件，如果该测试通过，则还将测试该文件是否为常规文件(-type f)。最后，使用已经遇到的操作-print来确认找到了哪些文件。
当你想找到与你的规范匹配的文件时，可以执行以下操作：
\begin{table}[ht]
\caption{}                    
\centering
	\begin{tabular}{|c|p{5cm}|}   
\hline
        Action & Meaning \\
\hline
	-exec command & Execute a command. This is one of the most common actions. See the explanation following this table for how parameters may be passed to the command. This action must be terminated with a \textbackslash; character pair. \\
\hline
	-ok command & Like -exec, except that it prompts for user confirmation of each file on which it will carry out the command before executing the command. This action must be terminated with a \textbackslash; character pair. \\  
\hline
	-print & Print out the name of the file. \\
\hline
	-la & Use the command ls -dils on the current file. \\
\hline
\end{tabular}
\end{table}
-exec和-ok命令将行上的后续参数作为其参数的一部分，直到以\textbackslash;序列。实际上，-exec和-ok命令正在执行嵌入式com命令，因此嵌入式命令必须以转义分号结束，以便find命令可以确定何时应该继续查找用于自身的命令行选项。魔术字符串“{}”是-exec或-ok命令的一种特殊类型的参数，它被替换为当前文件的完整路径。
grep命令
第二个非常有用的命令是grep，这是一个不常见的名称，代表通用正则表达式解析器。您使用find在系统中搜索文件，而使用grep在文件中搜索字符串。实际上，在使用find时，将grep作为在-exec之后传递的命令是很常见的.
grep命令接受选项、要匹配的模式和要搜索的文件:grep [options] PATTERN [FILES]
如果没有给出文件名，则搜索标准输入。主要的选项见下表：
\begin{table}[ht]
\caption{}                    
\centering
	\begin{tabular}{|c|p{5cm}|}   
\hline
        Option & Meaning \\
\hline
        -C & Rather than print matching lines, print a count of the number of lines that match \\
\hline
	-E & Turn on extended expressions. \\    
\hline
	-h & Suppress the normal prefixing of each output line with the name of the file it was found in\\
\hline
	-i & Ignore case.\\
\hline
	-l & List the names of the files with matching lines; don’t output the actual matched line. \\
\hline 
	-v & Invert the matching pattern to select nonmatching lines, rather than matching lines. \\
\hline
\end{tabular}
\end{table}

\bibliographystyle{plain}
\bibliography{crazyfish.bib}


\end{document}
